﻿using Moq;
using System;
using System.Linq;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Controllers;
using SportsStore.WebUI.Models;

namespace SportsStore.UnitTests
{
    [TestClass]
    public class CartTests
    {
        [TestMethod]
        public void Can_Add_New_Lines()
        {
            // Arrange - create some products
            var product1 = new Product { ProductID = 1, Name = "P1" };
            var product2 = new Product { ProductID = 2  , Name = "P2" };

            // Arrange - create new cart
            var cart = new Cart();

            // Act
            cart.AddItem( product1, 1 );
            cart.AddItem( product2, 1 );
            var results = cart.Lines.ToArray();

            // Assert
            Assert.AreEqual( 2, results.Length );
            Assert.AreEqual( product1, results[ 0 ].Product );
            Assert.AreEqual( product2, results[ 1 ].Product );
        }

        [TestMethod]
        public void Can_Add_Quantity_For_Existing_Lines()
        {
            // Arrange - create some test products
            var product1 = new Product { ProductID = 1, Name = "P1" };
            var product2 = new Product { ProductID = 2, Name = "P2" };

            // Arrange - create new cart
            var cart = new Cart();

            // Act
            cart.AddItem( product1, 1 );
            cart.AddItem( product2, 1 );
            cart.AddItem( product1, 10 );
            var results = cart.Lines.OrderBy( line => line.Product.ProductID ).ToArray();

            // Assert
            Assert.AreEqual( results.Length, 2 );
            Assert.AreEqual( results[ 0 ].Quantity, 11 );
            Assert.AreEqual( results[ 1 ].Quantity, 1 );
        }

        [TestMethod]
        public void Can_Remove_line()
        {
            // Arrange - Create some test products
            var product1 = new Product { ProductID = 1, Name = "P1" };
            var product2 = new Product { ProductID = 2, Name = "P2" };
            var product3 = new Product { ProductID = 3, Name = "P3" };

            // Arrange - create new cart
            var cart = new Cart();

            // Arrange - add some products
            cart.AddItem( product1, 1 );
            cart.AddItem( product2, 3 );
            cart.AddItem( product3, 5 );
            cart.AddItem( product2, 1 );

            // Act 
            cart.RemoveLine( product2 );

            // Assert
            Assert.AreEqual( 0, cart.Lines.Where( line => line.Product == product2 ).Count() );
            Assert.AreEqual( 2, cart.Lines.Count() );
        }

        [TestMethod]
        public void Calculate_Cart_Total()
        {
            // Arrange - create some products
            var product1 = new Product { ProductID = 1, Name = "P1", Price = 100M };
            var product2 = new Product { ProductID = 2, Name = "P2", Price = 50M };

            // Arrange - create new cart
            var cart = new Cart();

            // Act
            cart.AddItem( product1, 1 );
            cart.AddItem( product2, 1 );
            cart.AddItem( product1, 3 );
            var results = cart.ComputeTotalValue();

            // Assert
            Assert.AreEqual( 450M, results );
        }

        [TestMethod]
        public void Can_Clear_Contents()
        {
            // Arrange - create some test products
            var product1 = new Product { ProductID = 1, Name = "P1", Price = 100M };
            var product2 = new Product { ProductID = 2, Name = "P2", Price = 50M };

            // Arrange - create new cart
            var cart = new Cart();

            // Arrange - add some items
            cart.AddItem( product1, 1 );
            cart.AddItem( product2, 1 );

            // Act
            cart.Clear();

            // Assert
            Assert.AreEqual( 0, cart.Lines.Count() );
        }

        [TestMethod]
        public void Can_Add_To_Cart()
        {
            // Arrange - create the method repostiory
            var mock = new Mock<IProductRepository>();
            mock.Setup( repo => repo.Products )
                .Returns( new Product[]
                {
                    new Product{ ProductID = 1, Name = "P1", Category = "Apples" }
                }.AsQueryable() );
            
            // Arrange - Create a cart
            var cart = new Cart(); 

            // Arrange - create controller
            var cartController = new CartController( mock.Object, null );

            // Act - add a product to the cart
            cartController.AddToCart( cart, 1, null );

            // Assert 
            Assert.AreEqual( 1, cart.Lines.Count() );
            Assert.AreEqual( 1, cart.Lines.ToArray()[ 0 ].Product.ProductID );
        }

        [TestMethod]
        public void Adding_Product_To_Cart_Goes_To_Cart_Screen()
        {
            // Arrange - create the method repostiory
            var mock = new Mock<IProductRepository>();
            mock.Setup( repo => repo.Products )
                .Returns( new Product[]
                {
                    new Product{ ProductID = 1, Name = "P1", Category = "Apples" }
                }.AsQueryable() );

            // Arrange - Create a cart
            var cart = new Cart();

            // Arrange - create controller
            var cartController = new CartController( mock.Object, null );

            // Act - add a product to the cart
            RedirectToRouteResult result = cartController.AddToCart( cart, 2, "myURL" );

            // Assert 
            Assert.AreEqual( "Index", result.RouteValues[ "action" ] );
            Assert.AreEqual( "myURL", result.RouteValues[ "returnUrl" ] );
        }

        [TestMethod]
        public void Can_View_Cart_Contents()
        {
            // Arrange - Create a cart
            var cart = new Cart();

            // Arrange - create controller
            var cartController = new CartController( null, null );

            // Act - add a product to the cart
            var result = (CartIndexViewModel) cartController.Index( cart, "myURL" ).ViewData.Model;
            
            // Assert
            Assert.AreSame( cart, result.Cart );
            Assert.AreEqual( "myURL", result.ReturnUrl );
        }

        [TestMethod]
        public void Cannot_Checkout_EmptyCart()
        {
            // Arrange - create a mock order processor
            Mock<IOrderProcessor> mock = new Mock<IOrderProcessor>();
            var cart = new Cart();
            var shippingDetails = new ShippingDetails();
            var cartController = new CartController( null, mock.Object );

            // Act
            var viewResult = cartController.Checkout( cart, shippingDetails );

            // Assert - check that the order hasn't been passed on to the processor
            mock.Verify( orderProcessor => orderProcessor
                .ProcessOrder( It.IsAny<Cart>(), 
                               It.IsAny<ShippingDetails>() ), 
                Times.Never() );

            // Assert - check that the method is returning the default view 
            Assert.AreEqual( "", viewResult.ViewName );

            // Assert - check that we are passing an invalid model to the view
            Assert.AreEqual( false, viewResult.ViewData.ModelState.IsValid );
        }

        [TestMethod]
        public void Cannot_Checkout_Invalid_ShippingDetails()
        {
            // Arrange - create a mock order processor
            var mock = new Mock<IOrderProcessor>();
            var cart = new Cart();
            cart.AddItem( new Product(), 1 );

            // Arrange - create an instance of the controler
            var cartController = new CartController( null, mock.Object );
            // Arrange - add an error to the model
            cartController.ModelState.AddModelError( "error", "error" );

            // Act - try to checkout
            var result = cartController.Checkout( cart, new ShippingDetails() );

            // Assert - check that the order hasn't been passed on to the processor
            mock.Verify( orderProcessor => orderProcessor.ProcessOrder( It.IsAny<Cart>(), It.IsAny<ShippingDetails>() ), Times.Never() );
            // Assert - check out that the order hasn't been passed on to the processor
            Assert.AreEqual( "", result.ViewName );
            // Assert - check that we are passing an invalid model to the view
            Assert.AreEqual( false, result.ViewData.ModelState.IsValid );
        }

        [TestMethod]
        public void Can_Checkout_And_Submit_Order()
        {
            // Arrange- create a mock order processor
            var mock = new Mock<IOrderProcessor>();
            var cart = new Cart();
            cart.AddItem( new Product(), 1 );
            var cartController = new CartController( null, mock.Object );

            // Act - try to checkout
            var viewResult = cartController.Checkout( cart, new ShippingDetails() );

            // Assert - check that the order has been passed on to the processor
            mock.Verify( orderProcessor => orderProcessor.ProcessOrder( It.IsAny<Cart>(), It.IsAny<ShippingDetails>() ), Times.Once() );
            // Assert -  check that the method is returning the Completed view
            Assert.AreEqual( "Completed", viewResult.ViewName );
            // Assert - check that we are passing a vaild model to the view
            Assert.AreEqual( true, viewResult.ViewData.ModelState.IsValid );
        }
    }
}
